#include "net/EPoll.hxx"
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include <unistd.h>
#include <fcntl.h>
#include <sys/eventfd.h>
#include <stdio.h>

using namespace kiwii::net;

class EPollTest : public ::testing::Test {
protected:
  EPollTest() {
    pollInstance_ = new EPoll;
    fd1_ = initfd();
    fd2_ = initfd();
    fd3_ = initfd();

    EXPECT_GT(fd1_, 0);
    EXPECT_GT(fd2_, 0);
    EXPECT_GT(fd3_, 0);
  }

  ~EPollTest() {
    delete pollInstance_;
    close(fd1_);
    close(fd2_);
    close(fd3_);
  }

  int initfd() {
    int fd = eventfd(0, 0);
    if (fd < 0) {
      perror("eventfd");
      return -1;
    }
    int flags;
    if ((flags = fcntl(fd, F_GETFL)) < 0) {
      perror("fcntl F_GETFL");
      return -1;
    }
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
      perror("fcntl F_SETFL");
      return -1;
    }
    return fd;
  }

  PollScheme* pollInstance_;
  int fd1_;
  int fd2_;
  int fd3_;
  std::vector<FiredIOEvent> fired_;
};

TEST_F(EPollTest, WaitForIOEvent) {
  pollInstance_->addEvent(fd1_, EV_READABLE, EV_NONE);
  pollInstance_->addEvent(fd1_, EV_WRITABLE, EV_READABLE);
  pollInstance_->addEvent(fd2_, EV_WRITABLE, EV_NONE);
  pollInstance_->addEvent(fd3_, EV_READABLE | EV_WRITABLE, EV_NONE);
  uint64_t x = 1;
  write(fd1_, &x, sizeof(x));
  pollInstance_->poll(nullptr, fired_);

  ASSERT_EQ(3, (int)fired_.size());

  EXPECT_EQ(fd1_, fired_[0].fd_);
  EXPECT_TRUE(fired_[0].events_ & EV_READABLE);
  EXPECT_TRUE(fired_[0].events_ & EV_WRITABLE);

  EXPECT_EQ(fd2_, fired_[1].fd_);
  EXPECT_TRUE(fired_[1].events_ & EV_WRITABLE);
  EXPECT_FALSE(fired_[1].events_ & EV_READABLE);

  EXPECT_EQ(fd3_, fired_[2].fd_);
  EXPECT_TRUE(fired_[2].events_ & EV_WRITABLE);
  EXPECT_FALSE(fired_[2].events_ & EV_READABLE);
}

TEST_F(EPollTest, WaitForTimeOut) {
  pollInstance_->addEvent(fd1_, EV_READABLE, EV_NONE);
  pollInstance_->addEvent(fd2_, EV_READABLE | EV_WRITABLE, EV_NONE);
  uint64_t x = 1;
  write(fd2_, &x, sizeof(x));
  pollInstance_->poll(nullptr, fired_);

  ASSERT_EQ(1, (int)fired_.size());

  EXPECT_EQ(fd2_, fired_[0].fd_);
  EXPECT_TRUE(fired_[0].events_ & EV_READABLE);
  EXPECT_TRUE(fired_[0].events_ & EV_WRITABLE);

  read(fd2_, &x, sizeof(x));
  pollInstance_->poll(nullptr, fired_);

  ASSERT_EQ(1, (int)fired_.size());
  EXPECT_EQ(fd2_, fired_[0].fd_);
  EXPECT_TRUE(fired_[0].events_ & EV_WRITABLE);
  EXPECT_FALSE(fired_[0].events_ & EV_READABLE);

  pollInstance_->delEvent(fd2_, EV_WRITABLE, EV_READABLE | EV_WRITABLE);
  struct timeval t = {0, 10 * 1000}; //10 milliseconds
  pollInstance_->poll(&t, fired_);   //will timeout after 10 ms
  ASSERT_EQ(0, (int)fired_.size());
}
